Introducción a pandas, una librería para analizar datos

A petición de la comunidad, vamos a empezar a ver cómo podemos usar herramientas para analizar datos!!!

Pandas es una librería muy usada en "data science" ya que te permite cargar y operar rápidamente en python. En lo que se enfoca es en procesar arreglos de datos que tengan la forma n x m en donde "n" sean el número de filas (rows) y "m" el número de columnas. Cada columna puede contener más de un tipo de datos (no es como en numpy que todo tiene que ser lo mismo).

Ahora sin más preámbulos empecemos a usar pandas!!

Lo primero que hay que hacer es importar la librería. Para ello necesitamos que esté instalada. Todas las personas que tienen anaconda no deben de preocuparse por instalar ninguna otra cosa. El resto debe de hacerlo de acuerdo a como instaló python en un principio.


In [70]:
import pandas as pd
#Este comando lo que hace básicamente es decir que quiere accedeer a todas las clases
#que la librería pandas tiene
import numpy as np

Ahora que ya tenemos pandas podemos empezar a explorar que cosas podemos hacer con esta librería.

En pandas hay dos tipos nuevos de estructura de datos. Pregunta rápida, recuerdas los otros tipos de datos que hemos visto??

De regreso al punto :P los nuevos tipos de datos se separan en dos grupos: las series y los "data frames".

Las series son el elemento más simple en pandas ya que es una arreglo de n x 1 es decir n número de filas con una sola columna. Veamos un ejemplo:


In [71]:
lista = ['hola', 'mi', 'nombre', 'es', 'pandas',0.18 ]
serie = pd.Series(lista)
print(lista)
print(serie)


['hola', 'mi', 'nombre', 'es', 'pandas', 0.18]
0      hola
1        mi
2    nombre
3        es
4    pandas
5      0.18
dtype: object

Pregunta 1

¿Qué diferencias notas entre la lista y la serie?

Ajá!! Exactemente!! esos pequeños numerillos a la izquierda, ¿Qué te imaginas que son?

Pues esos pequeños se llaman índices y son un concepto muy útil que hay que entender porque pandas se maneja principalmente con índices. Ahora veamos como podemos nombrar como queramos a esos índices.


In [72]:
serie2 = pd.Series(lista, index=['H', 'm', 'n', 'e', 'p', '0'])
serie2


Out[72]:
H      hola
m        mi
n    nombre
e        es
p    pandas
0      0.18
dtype: object

Te recuerda esto a alguna estructura de datos que ya hemos visto?

ejem, ejem, dic..., ejem, ejem...

Pues si se podría pensar en una serie como una especie de diccionarios, de hecho para crear una serie también puedes partir de un diccionario...


In [73]:
diccionario = {'Ciudad de México':'Tacos al pastor', 'Puebla':'Chile en nogada', 'Yucatán':'Cochinita'}

In [74]:
serie_dic = pd.Series(diccionario)
serie_dic


Out[74]:
Ciudad de México    Tacos al pastor
Puebla              Chile en nogada
Yucatán                   Cochinita
dtype: object

In [101]:
serie_dic.loc['Puebla':'Yucatán']


Out[101]:
Puebla     Chile en nogada
Yucatán          Cochinita
dtype: object

In [75]:
lista2 = [1,1,1,1,1,1]

Pregunta 2

¿Qué crees que pase si queremos hacer una serie con la lista 1 pero que tenga como índices la lista 2?


In [77]:
ejemplo=pd.Series(lista, lista2)

In [78]:
ejemplo[1]


Out[78]:
1      hola
1        mi
1    nombre
1        es
1    pandas
1      0.18
dtype: object

Como pudiste darte cuenta, en realidad las series sí se parecen a los diccionarios pero no son exactamente lo mismo... Si quieres saber de qué otras maneras puedes crear series, puedes visitar la página de documentación oficial, está super completa!!

Ahora que ya conoces la materia prima de pandas podemos empezar a trabajar con data frames que si lo piensas, serían como un montón de series amarradas entre sí...

Para crear un data frame hay varias formas, puedes escribirlo todo de la misma forma que hicimos con las series pero también puedes importar una set de datos con el que quieras trabajar.

Veremos rápidamente como podemos crear un data frame y luego como podemos cargar uno que ya tengamos.


In [79]:
#Crear un data frame de un arreglo de numpy
arreglo = np.random.randn(7,3)
print(arreglo)
columnas = list('ABC')
data_frame = pd.DataFrame(arreglo, columns=columnas )


[[ -9.19971650e-01  -6.84530335e-01   8.22206005e-02]
 [ -1.27218830e+00  -2.85069739e+00  -1.53397822e-01]
 [ -2.58929584e+00  -4.04047056e-02   9.97718785e-01]
 [  7.24750698e-01   1.09092697e+00   4.09483440e-01]
 [  3.79047199e-01   7.64345627e-01  -1.90961377e-04]
 [  1.12118329e+00   1.04343252e+00  -1.61168674e-01]
 [ -6.13659711e-01  -1.54511010e+00  -2.15253189e-01]]

In [80]:
data_frame


Out[80]:
A B C
0 -0.919972 -0.684530 0.082221
1 -1.272188 -2.850697 -0.153398
2 -2.589296 -0.040405 0.997719
3 0.724751 1.090927 0.409483
4 0.379047 0.764346 -0.000191
5 1.121183 1.043433 -0.161169
6 -0.613660 -1.545110 -0.215253

Si quieres saber mas detalles de lo que la función random.randn de numpy hace, puedes checar esta página

Así de simple se puede generar un data frame, pero también podemos hacerlo con diccionarios...


In [81]:
df_diccionario = pd.DataFrame({'A':list(range(5)),
                              'B': np.random.random_sample(5),
                              'C':'Jueves'})

In [82]:
df_diccionario


Out[82]:
A B C
0 0 0.429676 Jueves
1 1 0.700321 Jueves
2 2 0.383765 Jueves
3 3 0.069433 Jueves
4 4 0.938380 Jueves

Así de simple es crear un data frame. Ahora veamos lo que todos estaban esperando!! Crear data frames a partir de datos que esten en hojas de excel o en csv o txt. Que justamente son los que se generan en los experimentos que algunas de ustedes lleguen a hacer o que otras personas hagan para que ustedes los analicen.

Vamos a ver un ejemplo con un set de datos pequeño.


In [83]:
mi_df = pd.read_csv('FreeFattyAcids.csv', header=0)
mi_df


Out[83]:
Sample 12:0 14:0 15:0 16:0 16:1 17:0 17:1 18:0 18:1 ... 22:4 22:5 N3 22:5 N6 22:6 23:0 24:0 24:1 26:0 Unnamed: 34 Total
0 C 0.0 152.3 46.6 1136.7 46.0 46.8 11.6 775.8 361.3 ... 2.2 0.9 2.2 8.8 0.0 2.5 1.0 1.6 NaN 2766.8
1 C 0.0 106.2 36.7 839.7 46.6 38.2 9.2 557.7 290.8 ... 3.1 1.9 3.1 12.7 0.0 2.4 1.1 0.7 NaN 2097.5
2 C 0.0 90.6 36.1 811.4 34.0 36.5 8.3 582.6 235.1 ... 0.6 1.1 1.2 8.9 0.0 1.2 1.0 1.2 NaN 1954.2
3 C 0.0 98.4 39.0 770.1 33.0 38.9 8.4 619.5 212.6 ... 1.6 1.0 1.0 8.5 0.0 1.5 0.8 1.2 NaN 1937.4
4 T1 0.0 135.0 46.3 1071.8 53.3 46.3 12.6 683.8 339.2 ... 0.6 0.1 1.0 6.8 0.0 1.9 1.2 1.5 NaN 2506.6
5 T1 0.0 100.1 37.4 1132.0 37.0 39.7 9.5 1010.2 263.6 ... 0.9 0.7 1.6 9.2 0.0 1.1 0.8 0.5 NaN 2744.2
6 T1 0.0 461.7 258.5 1802.0 831.4 96.4 74.4 688.2 704.1 ... 1.3 0.0 0.0 6.2 2.6 30.7 1.4 29.8 NaN 5246.3
7 T2 0.0 182.8 58.1 1507.0 67.0 49.3 17.1 790.8 442.8 ... 0.3 0.4 0.8 8.3 0.0 4.7 1.4 1.7 NaN 3236.9
8 T2 0.0 92.9 36.2 781.8 35.3 34.0 8.3 490.9 236.1 ... 0.0 0.3 0.2 7.0 0.0 1.3 0.9 0.3 NaN 1790.4
9 T2 0.0 77.5 34.1 742.0 36.8 34.3 8.5 571.2 223.6 ... 0.1 0.9 0.5 8.2 0.0 1.3 1.0 0.0 NaN 1805.9

10 rows × 36 columns

Este set de datos es de un perfil lipídico obtenido con espectrometría de masas. Son tres condiciones, una control y dos tratamientos, para cada una de ellas se puede ver la concentración de ácidos grasos de cadenas de 12 hasta 26 carbonos.

Por suerte este data frame es pequeño y podemos visualizar casi todo, pero imaginemos que tenemos 1000000 de columnas, en realidad no necesitamos verlo todo, pero queremos saber más o menos como está organizado nuestro data frame. Para esto podemos acceder a un método de mi_df para ver nada más la "cabeza" (o la "cola") de nuestros datos.


In [84]:
mi_df.head()


Out[84]:
Sample 12:0 14:0 15:0 16:0 16:1 17:0 17:1 18:0 18:1 ... 22:4 22:5 N3 22:5 N6 22:6 23:0 24:0 24:1 26:0 Unnamed: 34 Total
0 C 0.0 152.3 46.6 1136.7 46.0 46.8 11.6 775.8 361.3 ... 2.2 0.9 2.2 8.8 0.0 2.5 1.0 1.6 NaN 2766.8
1 C 0.0 106.2 36.7 839.7 46.6 38.2 9.2 557.7 290.8 ... 3.1 1.9 3.1 12.7 0.0 2.4 1.1 0.7 NaN 2097.5
2 C 0.0 90.6 36.1 811.4 34.0 36.5 8.3 582.6 235.1 ... 0.6 1.1 1.2 8.9 0.0 1.2 1.0 1.2 NaN 1954.2
3 C 0.0 98.4 39.0 770.1 33.0 38.9 8.4 619.5 212.6 ... 1.6 1.0 1.0 8.5 0.0 1.5 0.8 1.2 NaN 1937.4
4 T1 0.0 135.0 46.3 1071.8 53.3 46.3 12.6 683.8 339.2 ... 0.6 0.1 1.0 6.8 0.0 1.9 1.2 1.5 NaN 2506.6

5 rows × 36 columns


In [85]:
#Por default nos va a mostras las primeras 5 filas después del encabezado,
#pero podemos cambiarlo pasando un argumento a head()
mi_df.head(3)


Out[85]:
Sample 12:0 14:0 15:0 16:0 16:1 17:0 17:1 18:0 18:1 ... 22:4 22:5 N3 22:5 N6 22:6 23:0 24:0 24:1 26:0 Unnamed: 34 Total
0 C 0.0 152.3 46.6 1136.7 46.0 46.8 11.6 775.8 361.3 ... 2.2 0.9 2.2 8.8 0.0 2.5 1.0 1.6 NaN 2766.8
1 C 0.0 106.2 36.7 839.7 46.6 38.2 9.2 557.7 290.8 ... 3.1 1.9 3.1 12.7 0.0 2.4 1.1 0.7 NaN 2097.5
2 C 0.0 90.6 36.1 811.4 34.0 36.5 8.3 582.6 235.1 ... 0.6 1.1 1.2 8.9 0.0 1.2 1.0 1.2 NaN 1954.2

3 rows × 36 columns


In [86]:
#Para ver la "cola"
mi_df.tail(3)


Out[86]:
Sample 12:0 14:0 15:0 16:0 16:1 17:0 17:1 18:0 18:1 ... 22:4 22:5 N3 22:5 N6 22:6 23:0 24:0 24:1 26:0 Unnamed: 34 Total
7 T2 0.0 182.8 58.1 1507.0 67.0 49.3 17.1 790.8 442.8 ... 0.3 0.4 0.8 8.3 0.0 4.7 1.4 1.7 NaN 3236.9
8 T2 0.0 92.9 36.2 781.8 35.3 34.0 8.3 490.9 236.1 ... 0.0 0.3 0.2 7.0 0.0 1.3 0.9 0.3 NaN 1790.4
9 T2 0.0 77.5 34.1 742.0 36.8 34.3 8.5 571.2 223.6 ... 0.1 0.9 0.5 8.2 0.0 1.3 1.0 0.0 NaN 1805.9

3 rows × 36 columns

Pero una vez que tenemos los datos ¿qué hacemos con ellos? Pues bien en realidad eso depende mucho de las preguntas que queramos responder empecemos con unas preguntas fáciles.

Pregunta 3

Digamos que a mí sólo me interesa saber cómo cambia el total, ¿Cómo puedo hacer que en un data frame sólo me muestre la columna "Sample" y la columna "Total"?

Para responder esta cuestión, necesitamos saber como hacer "slicing" de los data frames, esto es, sacar subgrupos de un data frame para analizarlo más fácilmente. Alguien tiene alguna idea que podamos probar??


In [87]:
mi_df[0:5]


Out[87]:
Sample 12:0 14:0 15:0 16:0 16:1 17:0 17:1 18:0 18:1 ... 22:4 22:5 N3 22:5 N6 22:6 23:0 24:0 24:1 26:0 Unnamed: 34 Total
0 C 0.0 152.3 46.6 1136.7 46.0 46.8 11.6 775.8 361.3 ... 2.2 0.9 2.2 8.8 0.0 2.5 1.0 1.6 NaN 2766.8
1 C 0.0 106.2 36.7 839.7 46.6 38.2 9.2 557.7 290.8 ... 3.1 1.9 3.1 12.7 0.0 2.4 1.1 0.7 NaN 2097.5
2 C 0.0 90.6 36.1 811.4 34.0 36.5 8.3 582.6 235.1 ... 0.6 1.1 1.2 8.9 0.0 1.2 1.0 1.2 NaN 1954.2
3 C 0.0 98.4 39.0 770.1 33.0 38.9 8.4 619.5 212.6 ... 1.6 1.0 1.0 8.5 0.0 1.5 0.8 1.2 NaN 1937.4
4 T1 0.0 135.0 46.3 1071.8 53.3 46.3 12.6 683.8 339.2 ... 0.6 0.1 1.0 6.8 0.0 1.9 1.2 1.5 NaN 2506.6

5 rows × 36 columns

En pandas una de las cosas más confusas (especialmente si tienes tiempo trabajando con numpy) es sacar subgrupos de datos. La forma en la que pandas está diseñado para esto es a través de índices y encabezados.


In [88]:
mi_df["Sample"]


Out[88]:
0     C
1     C
2     C
3     C
4    T1
5    T1
6    T1
7    T2
8    T2
9    T2
Name: Sample, dtype: object

In [89]:
#o algo padre que también es posible en pandas es acceder a columnas con puntos
mi_df.Sample


Out[89]:
0     C
1     C
2     C
3     C
4    T1
5    T1
6    T1
7    T2
8    T2
9    T2
Name: Sample, dtype: object

Pero si queremos seleccionar múltiples columnas separadas podemos hacer lo siguiente


In [104]:
mi_df[["Sample","16:0"]][0:4].plot()


Out[104]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fddb4e4b518>

In [105]:
import matplotlib.pyplot as plt

In [ ]:


In [92]:
mi_df[0:3]


Out[92]:
Sample 12:0 14:0 15:0 16:0 16:1 17:0 17:1 18:0 18:1 ... 22:4 22:5 N3 22:5 N6 22:6 23:0 24:0 24:1 26:0 Unnamed: 34 Total
0 C 0.0 152.3 46.6 1136.7 46.0 46.8 11.6 775.8 361.3 ... 2.2 0.9 2.2 8.8 0.0 2.5 1.0 1.6 NaN 2766.8
1 C 0.0 106.2 36.7 839.7 46.6 38.2 9.2 557.7 290.8 ... 3.1 1.9 3.1 12.7 0.0 2.4 1.1 0.7 NaN 2097.5
2 C 0.0 90.6 36.1 811.4 34.0 36.5 8.3 582.6 235.1 ... 0.6 1.1 1.2 8.9 0.0 1.2 1.0 1.2 NaN 1954.2

3 rows × 36 columns

Selección por índice

Para seleccionas sólo por índice tenemos que agregar la terminación .loc después del nombre de nuestro data frame y luego poner los índices que queramos con los nombres de las columnas que nos interesen.

Esta sección es de la documentación de pandas y dice lo siguiente acerca del atributo .loc.

Este atributos es el método primario de acceso (a los datos). Los siguientes son inputs válidos:

  1. Una etiqueta (que es estrictamente de los índices)
  2. Una lista o arreglo de etiquetas
  3. Un slice de etiquetas (nota, al contrario de lo que se hace en python, el número inicial y final están incluídos!)
  4. Un arreglo booleano
  5. Un "callable"

In [103]:
mi_df.loc[0:4,'Sample']


Out[103]:
0     C
1     C
2     C
3     C
4    T1
Name: Sample, dtype: object

In [95]:
#¿qué pasa si sólo pongo el nombre de la columna?
mi_df.loc['Sample']


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/usr/lib/python3.5/site-packages/pandas/core/indexing.py in _has_valid_type(self, key, axis)
   1394                 if key not in ax:
-> 1395                     error()
   1396             except TypeError as e:

/usr/lib/python3.5/site-packages/pandas/core/indexing.py in error()
   1389                 raise KeyError("the label [%s] is not in the [%s]" %
-> 1390                                (key, self.obj._get_axis_name(axis)))
   1391 

KeyError: 'the label [Sample] is not in the [index]'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-95-c3505ccf1fcd> in <module>()
      1 #¿qué pasa si sólo pongo el nombre de la columna?
----> 2 mi_df.loc['Sample']

/usr/lib/python3.5/site-packages/pandas/core/indexing.py in __getitem__(self, key)
   1294             return self._getitem_tuple(key)
   1295         else:
-> 1296             return self._getitem_axis(key, axis=0)
   1297 
   1298     def _getitem_axis(self, key, axis=0):

/usr/lib/python3.5/site-packages/pandas/core/indexing.py in _getitem_axis(self, key, axis)
   1464 
   1465         # fall thru to straight lookup
-> 1466         self._has_valid_type(key, axis)
   1467         return self._get_label(key, axis=axis)
   1468 

/usr/lib/python3.5/site-packages/pandas/core/indexing.py in _has_valid_type(self, key, axis)
   1401                 raise
   1402             except:
-> 1403                 error()
   1404 
   1405         return True

/usr/lib/python3.5/site-packages/pandas/core/indexing.py in error()
   1388                                     "key")
   1389                 raise KeyError("the label [%s] is not in the [%s]" %
-> 1390                                (key, self.obj._get_axis_name(axis)))
   1391 
   1392             try:

KeyError: 'the label [Sample] is not in the [index]'

In [98]:
mi_df.loc[:, 'Sample']


Out[98]:
0     C
1     C
2     C
3     C
4    T1
5    T1
6    T1
7    T2
8    T2
9    T2
Name: Sample, dtype: object

In [ ]:
mi_df.loc[mi_df.Sample=='C']#Esto es un slicing dado un arreglo booleano

Selección por posición.

Es la más parecida a como opera numpy, y aquí sólo tienes que darle las posiciones que quieres (en números) de filas y columnas. Para esto usamos el atributo .iloc después de nuestro data frame.

Aquí los argumentos válidos son los siguientes:

  1. integer e.g. 5
  2. Lista o arreglo de enteros [4, 3, 0]
  3. Un objeto slice con ints 1:7
  4. Un arreglo booleano
  5. Un "callable"

In [ ]:
mi_df.iloc[0:3, -1]

In [ ]:

Ahora si hagamos unos ejercicios pa' que amarre

Ejercicio 1

Has un df que tenga sólo las columnas Sample y Total


In [ ]:


In [ ]:

Ejercicio 2

Muestrame que Sample tiene el mayor número de ácidos grasos de cadena 18:0


In [ ]:


In [ ]:

Ejercicio 3

Cuáles son los tres samples con mayor número de ácidos grasos libres


In [ ]:


In [ ]:

Pues esto es todo por hoy, espero que no haya sido tan complicado, podemos seguir aprendiendo de pandas si es que hay interes en el grupo y nos vemos en la siguiente reunión.

Gracias por asistir!!!


In [ ]: